[Feat]#46 트래킹 푸시 #55
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
1b1d692 to
7a65656
Compare
a740530 to
82b5895
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
src/main/java/com/semosan/api/domain/tracking/controller/docs/TrackingPhotoControllerDocs.java (1)
45-50: ⚡ Quick winAdd error response documentation for consistency.
The
listendpoint documentation is missing@ApiResponsesto document error cases. Based on the service implementation, this endpoint can return 403 (Forbidden) when accessing another user's session and 404 (Not Found) when the session doesn't exist. Document these for consistency with theuploadendpoint.📝 Suggested documentation addition
`@Operation`(summary = "트래킹 세션의 사진 목록 조회", description = "본인 소유 세션의 마일스톤 사진을 milestone_index 오름차순으로 반환합니다.") +@ApiResponses({ + `@io.swagger.v3.oas.annotations.responses.ApiResponse`(responseCode = "200", description = "사진 목록 조회 성공"), + `@io.swagger.v3.oas.annotations.responses.ApiResponse`(responseCode = "403", description = "본인 세션 아님", + content = `@Content`(schema = `@Schema`(implementation = ApiResponse.class))), + `@io.swagger.v3.oas.annotations.responses.ApiResponse`(responseCode = "404", description = "세션 없음", + content = `@Content`(schema = `@Schema`(implementation = ApiResponse.class))) +}) ResponseEntity<ApiResponse<List<TrackingPhotoResponse>>> list(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/semosan/api/domain/tracking/controller/docs/TrackingPhotoControllerDocs.java` around lines 45 - 50, Add ApiResponses to the TrackingPhotoControllerDocs.list method to document the error cases (403 and 404) the service can return: add an `@ApiResponses` annotation containing `@ApiResponse`(responseCode = "403", description = "Forbidden - accessing another user's session") and `@ApiResponse`(responseCode = "404", description = "Not Found - session does not exist") so the list operation's OpenAPI docs match the upload endpoint; update the annotations on the TrackingPhotoControllerDocs.list declaration accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/semosan/api/domain/tracking/dto/request/TrackingPhotoUploadRequest.java`:
- Around line 18-31: In TrackingPhotoUploadRequest add numeric bounds validation
to the Double fields: annotate milestoneDistanceM with a non-negative constraint
(e.g., `@DecimalMin`(value="0.0", inclusive=true) or `@PositiveOrZero` and an
optional sensible upper bound via `@DecimalMax`), and annotate lat and lng with
latitude/longitude bounds (lat: `@DecimalMin`(value="-90.0") and
`@DecimalMax`(value="90.0"); lng: `@DecimalMin`(value="-180.0") and
`@DecimalMax`(value="180.0")). Apply these annotations to the existing fields
(milestoneDistanceM, lat, lng) in class TrackingPhotoUploadRequest and add the
corresponding javax.validation/hibernate-validator imports so validation
triggers on incoming requests.
In
`@src/main/java/com/semosan/api/domain/tracking/service/TrackingPhotoService.java`:
- Around line 37-39: The existence check in TrackingPhotoService (uses
trackingPhotoRepository.existsByTrackingSession_IdAndMilestoneIndex) is not
atomic with the subsequent save and can allow race-condition duplicates; add a
DB-level unique constraint in the migration (003_tracking_photos.sql) to enforce
uniqueness on (tracking_session_id, milestone_index) by adding the unique
constraint named uk_tracking_photos_session_milestone so concurrent inserts
cannot create duplicate tracking photos.
In
`@src/main/java/com/semosan/api/domain/tracking/service/TrackingPhotoTriggerService.java`:
- Around line 74-83: Replace the current read-then-send-then-write flow with an
atomic gate using the SADD result: call
redisTemplate.opsForSet().add(openedKey(sessionId), idxStr) (and similarly for
closedKey) and check that the returned Long equals 1 before calling
sendOpen/sendClosed; only when add returns 1, invoke sendOpen/sendClosed and set
the TTL with redisTemplate.expire(openedKey(sessionId), TTL) (or closedKey) so
emission is performed only once under concurrency. Ensure you still use the same
idxStr, sessionId, i, mi parameters when calling sendOpen/sendClosed.
In
`@src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java`:
- Line 67: The create flow in TrackingSessionService currently calls
photoTriggerService.initializeMilestones(saved) inside the DB transaction which
couples session creation to Redis work; move the Redis call out of the
transactional boundary so DB commit cannot be rolled back by Redis failures.
Concretely, keep the DB save logic in the existing create method (or
`@Transactional` block) but either (a) invoke
photoTriggerService.initializeMilestones(saved) after the transaction commits
(use TransactionSynchronizationManager.registerSynchronization(... onAfterCommit
...) or call it from the caller after create returns) or (b) run
initializeMilestones asynchronously in a try/catch and log failures so Redis
errors don’t throw back into the transaction; reference
TrackingSessionService.create (or the method that saves `saved`) and the
photoTriggerService.initializeMilestones(saved) call when applying the change.
In
`@src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java`:
- Around line 56-64: The current code lets parsing userId or photoTrigger
failures abort the main GPS ingestion path; change TrackingStreamConsumer so
statsService.recordPoint(sessionId, lat, lng, altitude, recordedAt) always runs
and any photo-trigger work is best-effort: parse
latitude/longitude/altitude/recordedAt as before and call
statsService.recordPoint first, then wrap parsing of userId and the call to
photoTriggerService.evaluate(sessionId, userId, distanceTotal) in a try-catch
block that logs but swallows exceptions; ensure any NumberFormatException or
DateTimeParseException from userId parsing or photoTriggerService.evaluate does
not propagate and does not prevent point buffering/flush.
In `@src/main/resources/db/migration/003_tracking_photos.sql`:
- Around line 25-26: The current non-unique index
idx_tracking_photos_session_milestone on tracking_photos (tracking_session_id,
milestone_index) allows duplicate rows under concurrent inserts; change it to
enforce uniqueness at the DB level by creating a UNIQUE index or adding a UNIQUE
constraint on (tracking_session_id, milestone_index) (replace the existing
idx_tracking_photos_session_milestone creation with a UNIQUE variant or use
ALTER TABLE ... ADD CONSTRAINT unique_tracking_photos_session_milestone UNIQUE
(tracking_session_id, milestone_index)); ensure the migration is written in a
safe way for your DB (use CREATE UNIQUE INDEX IF NOT EXISTS or the appropriate
ALTER TABLE statement).
---
Nitpick comments:
In
`@src/main/java/com/semosan/api/domain/tracking/controller/docs/TrackingPhotoControllerDocs.java`:
- Around line 45-50: Add ApiResponses to the TrackingPhotoControllerDocs.list
method to document the error cases (403 and 404) the service can return: add an
`@ApiResponses` annotation containing `@ApiResponse`(responseCode = "403",
description = "Forbidden - accessing another user's session") and
`@ApiResponse`(responseCode = "404", description = "Not Found - session does not
exist") so the list operation's OpenAPI docs match the upload endpoint; update
the annotations on the TrackingPhotoControllerDocs.list declaration accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b501449e-9dc4-4fa6-ab7c-6c8333d36593
📒 Files selected for processing (16)
src/main/java/com/semosan/api/common/status/ErrorStatus.javasrc/main/java/com/semosan/api/common/status/SuccessStatus.javasrc/main/java/com/semosan/api/domain/notification/enums/NotificationType.javasrc/main/java/com/semosan/api/domain/tracking/controller/TrackingPhotoController.javasrc/main/java/com/semosan/api/domain/tracking/controller/docs/TrackingPhotoControllerDocs.javasrc/main/java/com/semosan/api/domain/tracking/dto/request/TrackingPhotoUploadRequest.javasrc/main/java/com/semosan/api/domain/tracking/dto/response/TrackingPhotoResponse.javasrc/main/java/com/semosan/api/domain/tracking/entity/TrackingPhoto.javasrc/main/java/com/semosan/api/domain/tracking/repository/TrackingPhotoRepository.javasrc/main/java/com/semosan/api/domain/tracking/service/TrackingMilestoneCalculator.javasrc/main/java/com/semosan/api/domain/tracking/service/TrackingPhotoService.javasrc/main/java/com/semosan/api/domain/tracking/service/TrackingPhotoTriggerService.javasrc/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.javasrc/main/java/com/semosan/api/domain/tracking/service/TrackingSessionStatsService.javasrc/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.javasrc/main/resources/db/migration/003_tracking_photos.sql
| @NotNull(message = "마일스톤 기준 거리(m)는 필수입니다.") | ||
| Double milestoneDistanceM, | ||
|
|
||
| @NotBlank(message = "imageUrl 은 필수입니다.") | ||
| String imageUrl, | ||
|
|
||
| @NotNull(message = "촬영 시각은 필수입니다.") | ||
| LocalDateTime capturedAt, | ||
|
|
||
| @NotNull(message = "위도는 필수입니다.") | ||
| Double lat, | ||
|
|
||
| @NotNull(message = "경도는 필수입니다.") | ||
| Double lng, |
There was a problem hiding this comment.
Add bounds validation for distance and coordinates.
milestoneDistanceM, lat, and lng currently allow impossible values, so malformed payloads can be saved.
Suggested fix
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
@@
`@NotNull`(message = "마일스톤 기준 거리(m)는 필수입니다.")
+ `@Positive`(message = "마일스톤 기준 거리(m)는 0보다 커야 합니다.")
Double milestoneDistanceM,
@@
`@NotNull`(message = "위도는 필수입니다.")
+ `@DecimalMin`(value = "-90.0", message = "위도는 -90 이상이어야 합니다.")
+ `@DecimalMax`(value = "90.0", message = "위도는 90 이하여야 합니다.")
Double lat,
@@
`@NotNull`(message = "경도는 필수입니다.")
+ `@DecimalMin`(value = "-180.0", message = "경도는 -180 이상이어야 합니다.")
+ `@DecimalMax`(value = "180.0", message = "경도는 180 이하여야 합니다.")
Double lng,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @NotNull(message = "마일스톤 기준 거리(m)는 필수입니다.") | |
| Double milestoneDistanceM, | |
| @NotBlank(message = "imageUrl 은 필수입니다.") | |
| String imageUrl, | |
| @NotNull(message = "촬영 시각은 필수입니다.") | |
| LocalDateTime capturedAt, | |
| @NotNull(message = "위도는 필수입니다.") | |
| Double lat, | |
| @NotNull(message = "경도는 필수입니다.") | |
| Double lng, | |
| `@NotNull`(message = "마일스톤 기준 거리(m)는 필수입니다.") | |
| `@Positive`(message = "마일스톤 기준 거리(m)는 0보다 커야 합니다.") | |
| Double milestoneDistanceM, | |
| `@NotBlank`(message = "imageUrl 은 필수입니다.") | |
| String imageUrl, | |
| `@NotNull`(message = "촬영 시각은 필수입니다.") | |
| LocalDateTime capturedAt, | |
| `@NotNull`(message = "위도는 필수입니다.") | |
| `@DecimalMin`(value = "-90.0", message = "위도는 -90 이상이어야 합니다.") | |
| `@DecimalMax`(value = "90.0", message = "위도는 90 이하여야 합니다.") | |
| Double lat, | |
| `@NotNull`(message = "경도는 필수입니다.") | |
| `@DecimalMin`(value = "-180.0", message = "경도는 -180 이상이어야 합니다.") | |
| `@DecimalMax`(value = "180.0", message = "경도는 180 이하여야 합니다.") | |
| Double lng, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/semosan/api/domain/tracking/dto/request/TrackingPhotoUploadRequest.java`
around lines 18 - 31, In TrackingPhotoUploadRequest add numeric bounds
validation to the Double fields: annotate milestoneDistanceM with a non-negative
constraint (e.g., `@DecimalMin`(value="0.0", inclusive=true) or `@PositiveOrZero`
and an optional sensible upper bound via `@DecimalMax`), and annotate lat and lng
with latitude/longitude bounds (lat: `@DecimalMin`(value="-90.0") and
`@DecimalMax`(value="90.0"); lng: `@DecimalMin`(value="-180.0") and
`@DecimalMax`(value="180.0")). Apply these annotations to the existing fields
(milestoneDistanceM, lat, lng) in class TrackingPhotoUploadRequest and add the
corresponding javax.validation/hibernate-validator imports so validation
triggers on incoming requests.
| if (trackingPhotoRepository.existsByTrackingSession_IdAndMilestoneIndex(sessionId, request.milestoneIndex())) { | ||
| throw new GeneralException(ErrorStatus.TRACKING_PHOTO_DUPLICATE); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if the tracking_photos table has a unique constraint on session + milestone
rg -n "UNIQUE.*tracking_session_id.*milestone_index|milestone_index.*tracking_session_id" src/main/resources/db/migration/003_tracking_photos.sqlRepository: SEMOSAN/SEMOSAN_BE
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Find the migration file
fd "003_tracking_photos" src/
# Also check for any migration files related to tracking
echo "=== All migration files ==="
fd "\.sql$" src/main/resources/db/migration/ 2>/dev/null | head -20Repository: SEMOSAN/SEMOSAN_BE
Length of output: 320
🏁 Script executed:
#!/bin/bash
# If the file exists, read it to check the schema
if [ -f "src/main/resources/db/migration/003_tracking_photos.sql" ]; then
echo "=== Content of migration file ==="
cat "src/main/resources/db/migration/003_tracking_photos.sql"
else
echo "File not found at expected location. Searching for it..."
find . -name "*tracking_photos*" -type f 2>/dev/null
fiRepository: SEMOSAN/SEMOSAN_BE
Length of output: 1818
Add a database unique constraint to prevent duplicate tracking photos.
The existsByTrackingSession_IdAndMilestoneIndex check at line 37 and the subsequent save at line 50 are not atomic. Without database-level enforcement, two concurrent upload requests for the same milestone could both pass the existence check and proceed to save, creating duplicate entries.
The migration file 003_tracking_photos.sql currently only creates a composite index on (tracking_session_id, milestone_index) for query performance, but no UNIQUE constraint. Add a unique constraint to the migration to enforce this at the database level:
ALTER TABLE tracking_photos
ADD CONSTRAINT uk_tracking_photos_session_milestone
UNIQUE (tracking_session_id, milestone_index);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/semosan/api/domain/tracking/service/TrackingPhotoService.java`
around lines 37 - 39, The existence check in TrackingPhotoService (uses
trackingPhotoRepository.existsByTrackingSession_IdAndMilestoneIndex) is not
atomic with the subsequent save and can allow race-condition duplicates; add a
DB-level unique constraint in the migration (003_tracking_photos.sql) to enforce
uniqueness on (tracking_session_id, milestone_index) by adding the unique
constraint named uk_tracking_photos_session_milestone so concurrent inserts
cannot create duplicate tracking photos.
| if (!isOpened && distanceTotal >= entry && distanceTotal <= exit) { | ||
| sendOpen(sessionId, userId, i, mi); | ||
| redisTemplate.opsForSet().add(openedKey(sessionId), idxStr); | ||
| redisTemplate.expire(openedKey(sessionId), TTL); | ||
| } | ||
| if (isOpened && !isClosed && distanceTotal > exit) { | ||
| sendClosed(sessionId, i, mi); | ||
| redisTemplate.opsForSet().add(closedKey(sessionId), idxStr); | ||
| redisTemplate.expire(closedKey(sessionId), TTL); | ||
| } |
There was a problem hiding this comment.
Make OPEN/CLOSED emission atomic at Redis write time.
Line 74–83 currently does read-then-send-then-write, so concurrent instances can emit duplicate OPEN/CLOSED for the same milestone. Gate emission on SADD result (1 only) to keep idempotency under concurrency.
Suggested fix
- Set<String> opened = membersOrEmpty(openedKey(sessionId));
- Set<String> closed = membersOrEmpty(closedKey(sessionId));
-
for (int i = 0; i < milestones.size(); i++) {
String idxStr = String.valueOf(i);
double mi = milestones.get(i);
double entry = mi * (1 - TOLERANCE_RATIO);
double exit = mi * (1 + TOLERANCE_RATIO);
- boolean isOpened = opened.contains(idxStr);
- boolean isClosed = closed.contains(idxStr);
-
- if (!isOpened && distanceTotal >= entry && distanceTotal <= exit) {
- sendOpen(sessionId, userId, i, mi);
- redisTemplate.opsForSet().add(openedKey(sessionId), idxStr);
- redisTemplate.expire(openedKey(sessionId), TTL);
- }
- if (isOpened && !isClosed && distanceTotal > exit) {
- sendClosed(sessionId, i, mi);
- redisTemplate.opsForSet().add(closedKey(sessionId), idxStr);
- redisTemplate.expire(closedKey(sessionId), TTL);
- }
+ if (distanceTotal >= entry && distanceTotal <= exit) {
+ Long added = redisTemplate.opsForSet().add(openedKey(sessionId), idxStr);
+ if (Long.valueOf(1L).equals(added)) {
+ redisTemplate.expire(openedKey(sessionId), TTL);
+ sendOpen(sessionId, userId, i, mi);
+ }
+ }
+ if (distanceTotal > exit
+ && Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(openedKey(sessionId), idxStr))) {
+ Long addedClosed = redisTemplate.opsForSet().add(closedKey(sessionId), idxStr);
+ if (Long.valueOf(1L).equals(addedClosed)) {
+ redisTemplate.expire(closedKey(sessionId), TTL);
+ sendClosed(sessionId, i, mi);
+ }
+ }
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/semosan/api/domain/tracking/service/TrackingPhotoTriggerService.java`
around lines 74 - 83, Replace the current read-then-send-then-write flow with an
atomic gate using the SADD result: call
redisTemplate.opsForSet().add(openedKey(sessionId), idxStr) (and similarly for
closedKey) and check that the returned Long equals 1 before calling
sendOpen/sendClosed; only when add returns 1, invoke sendOpen/sendClosed and set
the TTL with redisTemplate.expire(openedKey(sessionId), TTL) (or closedKey) so
emission is performed only once under concurrency. Ensure you still use the same
idxStr, sessionId, i, mi parameters when calling sendOpen/sendClosed.
|
|
||
| TrackingSession session = TrackingSession.create(user, mountain, course, request.isFreeRecording()); | ||
| TrackingSession saved = trackingSessionRepository.save(session); | ||
| photoTriggerService.initializeMilestones(saved); |
There was a problem hiding this comment.
Do not couple session creation success to Redis milestone initialization.
Line 67 invokes external Redis work inside the create transaction. If that call fails, session creation fails/rolls back even though the core DB write already succeeded.
Suggested fix
TrackingSession session = TrackingSession.create(user, mountain, course, request.isFreeRecording());
TrackingSession saved = trackingSessionRepository.save(session);
- photoTriggerService.initializeMilestones(saved);
+ try {
+ photoTriggerService.initializeMilestones(saved);
+ } catch (RuntimeException e) {
+ // 보조 기능 실패가 세션 생성 자체를 막지 않도록 fail-open 처리
+ log.warn("Failed to initialize photo milestones. sessionId={}", saved.getId(), e);
+ }
return TrackingSessionResponse.from(saved);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java`
at line 67, The create flow in TrackingSessionService currently calls
photoTriggerService.initializeMilestones(saved) inside the DB transaction which
couples session creation to Redis work; move the Redis call out of the
transactional boundary so DB commit cannot be rolled back by Redis failures.
Concretely, keep the DB save logic in the existing create method (or
`@Transactional` block) but either (a) invoke
photoTriggerService.initializeMilestones(saved) after the transaction commits
(use TransactionSynchronizationManager.registerSynchronization(... onAfterCommit
...) or call it from the caller after create returns) or (b) run
initializeMilestones asynchronously in a try/catch and log failures so Redis
errors don’t throw back into the transaction; reference
TrackingSessionService.create (or the method that saves `saved`) and the
photoTriggerService.initializeMilestones(saved) call when applying the change.
| Long userId = Long.parseLong(body.get(F_USER_ID)); | ||
| double lat = Double.parseDouble(body.get(F_LAT)); | ||
| double lng = Double.parseDouble(body.get(F_LNG)); | ||
| Double altitude = parseNullableDouble(body.get(F_ALTITUDE)); | ||
| LocalDateTime recordedAt = LocalDateTime.parse(body.get(F_RECORDED_AT)); | ||
|
|
||
| statsService.recordPoint(sessionId, lat, lng, altitude, recordedAt); | ||
| double distanceTotal = statsService.recordPoint(sessionId, lat, lng, altitude, recordedAt); | ||
| photoTriggerService.evaluate(sessionId, userId, distanceTotal); | ||
|
|
There was a problem hiding this comment.
Isolate photo-trigger errors from core GPS ingestion path.
Line 56–64 lets userId parse or photo-trigger failures short-circuit the whole message, so point buffering/flush can be skipped. Keep stats + point persistence on the main path, and handle photo trigger as best-effort.
Suggested fix
- Long userId = Long.parseLong(body.get(F_USER_ID));
double lat = Double.parseDouble(body.get(F_LAT));
double lng = Double.parseDouble(body.get(F_LNG));
Double altitude = parseNullableDouble(body.get(F_ALTITUDE));
LocalDateTime recordedAt = LocalDateTime.parse(body.get(F_RECORDED_AT));
double distanceTotal = statsService.recordPoint(sessionId, lat, lng, altitude, recordedAt);
- photoTriggerService.evaluate(sessionId, userId, distanceTotal);
+ Long userId = parseNullableLong(body.get(F_USER_ID));
+ if (userId != null) {
+ try {
+ photoTriggerService.evaluate(sessionId, userId, distanceTotal);
+ } catch (RuntimeException e) {
+ log.warn("Photo trigger evaluation failed: sessionId={} userId={}", sessionId, userId, e);
+ }
+ }
@@
private static Double parseNullableDouble(String value) {
return (value == null || value.isEmpty()) ? null : Double.parseDouble(value);
}
+
+ private static Long parseNullableLong(String value) {
+ if (value == null || value.isEmpty()) return null;
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Long userId = Long.parseLong(body.get(F_USER_ID)); | |
| double lat = Double.parseDouble(body.get(F_LAT)); | |
| double lng = Double.parseDouble(body.get(F_LNG)); | |
| Double altitude = parseNullableDouble(body.get(F_ALTITUDE)); | |
| LocalDateTime recordedAt = LocalDateTime.parse(body.get(F_RECORDED_AT)); | |
| statsService.recordPoint(sessionId, lat, lng, altitude, recordedAt); | |
| double distanceTotal = statsService.recordPoint(sessionId, lat, lng, altitude, recordedAt); | |
| photoTriggerService.evaluate(sessionId, userId, distanceTotal); | |
| double lat = Double.parseDouble(body.get(F_LAT)); | |
| double lng = Double.parseDouble(body.get(F_LNG)); | |
| Double altitude = parseNullableDouble(body.get(F_ALTITUDE)); | |
| LocalDateTime recordedAt = LocalDateTime.parse(body.get(F_RECORDED_AT)); | |
| double distanceTotal = statsService.recordPoint(sessionId, lat, lng, altitude, recordedAt); | |
| Long userId = parseNullableLong(body.get(F_USER_ID)); | |
| if (userId != null) { | |
| try { | |
| photoTriggerService.evaluate(sessionId, userId, distanceTotal); | |
| } catch (RuntimeException e) { | |
| log.warn("Photo trigger evaluation failed: sessionId={} userId={}", sessionId, userId, e); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java`
around lines 56 - 64, The current code lets parsing userId or photoTrigger
failures abort the main GPS ingestion path; change TrackingStreamConsumer so
statsService.recordPoint(sessionId, lat, lng, altitude, recordedAt) always runs
and any photo-trigger work is best-effort: parse
latitude/longitude/altitude/recordedAt as before and call
statsService.recordPoint first, then wrap parsing of userId and the call to
photoTriggerService.evaluate(sessionId, userId, distanceTotal) in a try-catch
block that logs but swallows exceptions; ensure any NumberFormatException or
DateTimeParseException from userId parsing or photoTriggerService.evaluate does
not propagate and does not prevent point buffering/flush.
| CREATE INDEX IF NOT EXISTS idx_tracking_photos_session_milestone | ||
| ON tracking_photos (tracking_session_id, milestone_index); |
There was a problem hiding this comment.
Enforce session+milestone uniqueness at DB level.
This index is non-unique, so concurrent uploads can still insert duplicate photos for the same (tracking_session_id, milestone_index) despite application checks.
Suggested fix
-CREATE INDEX IF NOT EXISTS idx_tracking_photos_session_milestone
- ON tracking_photos (tracking_session_id, milestone_index);
+CREATE UNIQUE INDEX IF NOT EXISTS uq_tracking_photos_session_milestone
+ ON tracking_photos (tracking_session_id, milestone_index);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| CREATE INDEX IF NOT EXISTS idx_tracking_photos_session_milestone | |
| ON tracking_photos (tracking_session_id, milestone_index); | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_tracking_photos_session_milestone | |
| ON tracking_photos (tracking_session_id, milestone_index); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/resources/db/migration/003_tracking_photos.sql` around lines 25 -
26, The current non-unique index idx_tracking_photos_session_milestone on
tracking_photos (tracking_session_id, milestone_index) allows duplicate rows
under concurrent inserts; change it to enforce uniqueness at the DB level by
creating a UNIQUE index or adding a UNIQUE constraint on (tracking_session_id,
milestone_index) (replace the existing idx_tracking_photos_session_milestone
creation with a UNIQUE variant or use ALTER TABLE ... ADD CONSTRAINT
unique_tracking_photos_session_milestone UNIQUE (tracking_session_id,
milestone_index)); ensure the migration is written in a safe way for your DB
(use CREATE UNIQUE INDEX IF NOT EXISTS or the appropriate ALTER TABLE
statement).
[MERGE] #20 트래킹 기록 머지
…photo-push # Conflicts: # src/main/java/com/semosan/api/common/status/ErrorStatus.java # src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java # src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java
- 003_tracking_photos.sql → V10__create_tracking_photos.sql Flyway 컨벤션 통일 - BIGSERIAL → bigint identity, FK 제약 이름 명시(fk_tracking_photos_session) - timestamp(6), DEFAULT now() 제거 (BaseEntity 가 채움) - notifications.type CHECK 갱신 동봉 (TRACKING_PHOTO_MILESTONE 추가)
[Feat] 디스코드 서버 에러 알림 전송 기능 추가
[fix] #75 getNearbyMountain 메소드 파라미터 제약 수정
…onflict [Hotfix]#77 Swagger MountainInfo 스키마 충돌 해소
🧾 요약
🔗 이슈
✨ 변경 내용
tracking_photos테이블 신설 및notifications.type제약 갱신 마이그레이션 추가✅ 확인
Summary by CodeRabbit